//
// $Id: SampleManager.java,v 1.21 2004/08/27 18:51:26 mdb Exp $

package zachg.survival;

import java.awt.Point;
import java.util.ArrayList;

import zachg.survival.SurvivalObject.MatchPraise;

import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.data.BodyObject;

import com.threerings.parlor.game.server.GameManager;
import com.threerings.parlor.turn.server.TurnGameManager;
import com.threerings.parlor.turn.server.TurnGameManagerDelegate;

import com.threerings.toybox.data.ToyBoxGameConfig;
import com.threerings.util.Name;

/**
 * Handles the server side of the game.
 */
public class SurvivalManager extends GameManager
implements TurnGameManager
{
	
    public SurvivalManager ()
    {
        // we're a turn based game, so we use a turn game manager delegate
        addDelegate(_turndel = new TurnGameManagerDelegate() {
            protected void setNextTurnHolder () {
            	_currentGameTurn++;
            	
            	//Quick fix for first player going twice.
            	if(_currentGameTurn == 0){
            		_turnIdx = 0;
            	}
            	
            	//certain view functions need to know the current turn number, but can't access the server. 
            	//So we set that on the distributed object here
            	_gameobj.setCurrentTurn(_currentGameTurn);
            	PercentageGameComplete = _currentGameTurn/_gameTurnLength;

            	//After all players have gone, allow the creepers to have a turn, and trigger acts of god. :o
            	if(_currentGameTurn%_gameconf.players.length == 0 && _currentGameTurn != 0) {
            		creeperTurn();
            		_turnIdx = 0;
            	} else {
            		_turnIdx = _currentGameTurn%_gameconf.players.length;
            	}
            	
                //determine if it's time for a death event
        		if(_logic.isTimeForDeathEvent((int)_gameobj.currentTurn)){
        			//invoke on server
        			triggerActOfGod(null);
        		}
        		
        		//show event messages on client
        		if(_gameobj.matchPraise != null){
        			_gameobj.showMessageInClients();
        		}
        		
        		//null so that the next turn doesn't show the same message
                if(_gameobj.matchPraise != null){
                	_gameobj.setMatchPraise( new MatchPraise(0,0,0,"") );
                }        		
            }
        });
    }
 
    // from interface TurnGameManager
    public void turnWillStart ()
    {
        // nothing to do here
    }
 
    // from interface TurnGameManager
    public void turnDidStart ()
    {
        // nothing to do here
    }
 
    // from interface TurnGameManager
    public void turnDidEnd ()
    {
    	if(_currentGameTurn >= _gameTurnLength){
    		//GAME OVER
    		
    		//display message about who won.
    		ArrayList<SurvivalObject.GameCharacter> winningCharacters = _gameobj.determinePlayersWithMostPoints();
        	String winningText = "";
    		for(SurvivalObject.GameCharacter winningCharacter : winningCharacters){
    			winningText += winningCharacter.playerOwner + " ";
    		}
    		winningText += " won the game!!";
    		_gameobj.setMatchPraise(new SurvivalObject.MatchPraise(0, 0, 0, winningText));
    		_gameobj.showMessageInClients();
    		
    		endGame();
    	}
    }
    
    /**
     * Move creepers, let them attack, and add new ones
     */
    public void creeperTurn ()
    {
    	turnWillStart();
    	turnDidStart();
    	//move creeps
    	_logic.creeperTurn(_gameobj);
    	//make creepers attack
    	for(int i = 0 ; i < _logic._creeps.size() ; i++){
    		_gameobj.updateGameCharacters(_logic._creeps.get(i) );
    		if( _logic.canAttack(_gameobj.gameCharacters, _logic._creeps.get(i)) == true){
    			doAttack(_logic._creeps.get(i));
    		}
    	}
    	
    	//add new creeps
    	for(int i = 0 ; i < _logic.creepsPerTurn ; i++){
    		SurvivalObject.GameCharacter newCreeper = _logic.spawnAtRandomPosition(SurvivalObject.ENEMY_ID , new Name(SurvivalObject.CREEPER_NAME), _gameobj );
    		newCreeper = _logic.setInitialCreeperProperties(newCreeper, PercentageGameComplete);
    		_logic._creeps.add(newCreeper);
    		_gameobj.placeGameCharacter( newCreeper );
    	}
    	
    	turnDidEnd();
    }
 
    @Override // from PlaceManager
    public void didInit ()
    {
        super.didInit();

        // get a casted reference to our game configuration
        _gameconf = (ToyBoxGameConfig)_config;

        // create our game logic instance
        _logic = new SurvivalLogic(SurvivalObject.GAME_SIZE);
        
        // this is called when our manager is created but before any
        // game-specific actions take place; we don't yet have our game object
        // at this point but we do have our game configuration
    }

    @Override // from PlaceManager
    public void didStartup ()
    {
        super.didStartup();

        // grab our own casted game object reference
        _gameobj = (SurvivalObject)super._gameobj;

        // this method is called after we have created our game object but
        // before we do any game related things
    }

    @Override // from PlaceManager
    public void didShutdown ()
    {
        super.didShutdown();

        // this is called right before we finally disappear for good
    }

    @Override // from PlaceManager
    protected PlaceObject createPlaceObject ()
    {
        return new SurvivalObject();
    }

    @Override // from GameManager
    protected void gameWillStart ()
    {
        super.gameWillStart();

        // when all the players have entered the game room, the game is
        // automatically started and this method is called just before the
        // event is delivered to the clients that will start the game

        // this is the place to do any pre-game setup that needs to be done
        // each time a game is started rather than just once at the very
        // beginning (those sorts of things should be done in didStartup())
        
        _gameTurnLength = (Integer)_gameconf.params.get("gameLength");
        _gameobj.setTotalGameTurns(_gameTurnLength);
        
        //create mines
        _gameobj.set_mineLocations( new int[_logic.numMines*2] ); 
    	for( int i = 0 ; i < (_logic.numMines*2) ; i += 2) {
    		Point randomLocation = new Point((int)(Math.random()*SurvivalObject.GAME_SIZE), (int)(Math.random()*SurvivalObject.GAME_SIZE));
    		_gameobj.set_mineLocationsAt(randomLocation.x, i);
    		_gameobj.set_mineLocationsAt(randomLocation.y, i+1);
    	}        
        
        for ( int i = 0 ; i < _playerCount ; i++)
        {
        	_gameobj.placeGameCharacter( _logic.spawnAtRandomPosition(SurvivalObject.PLAYER_ID , _gameobj.players[i], _gameobj));
        }
    }

    @Override // from GameManager
    protected void gameDidEnd ()
    {
        super.gameDidEnd();

        // this is called after the game has ended. somewhere in the game
        // manager a call to endGame() should be made when the manager knows
        // the game to be over and that will trigger the end-of-game processing
        // including calling this method
    }

    /**
     * Game character mined.
     *
     * @param player the player
     * @param nextGameCharacterLocation the next game character location
     * @param gameCharacter the game character
     */
    public void gameCharacterMined(		BodyObject player, 
    									SurvivalObject.GameCharacter nextGameCharacterLocation, 
    									SurvivalObject.GameCharacter gameCharacter
    							  )
    {
    	
        // make sure it's this player's turn
        int pidx = _turndel.getTurnHolderIndex();
        if (_playerOids[pidx] != player.getOid()) {
            System.err.println("Requested to place piece by non-turn holder " +
                               "[who=" + player.who() +
                               ", turnHolder=" + _gameobj.turnHolder + "].");
  
        // make sure this is legal 
        } else if (	_logic.isLegalMove(nextGameCharacterLocation, gameCharacter) &&
        		  	_logic.canMine(nextGameCharacterLocation, _gameobj._mineLocations) == true) {
        	//move character to location.
        	gameCharacter.x = nextGameCharacterLocation.x;
        	gameCharacter.y = nextGameCharacterLocation.y;
        	_gameobj.updateGameCharacters(gameCharacter);
        	
        	//mine
        	doMine(gameCharacter);
        	
        	_turndel.endTurn();
  
        } else {
        	Log.info("illegal move");
            System.err.println("Received illegal move request " +
                               "[who=" + player.who() +
                               ", piece=" + gameCharacter + "].");
        }        
    }

    public void gameCharacterAttack(	BodyObject player, 
										SurvivalObject.GameCharacter nextGameCharacterLocation, 
										SurvivalObject.GameCharacter gameCharacter)
    {
        // make sure it's this player's turn
        int pidx = _turndel.getTurnHolderIndex();
        if (_playerOids[pidx] != player.getOid()) {
            System.err.println("Requested to place piece by non-turn holder " +
                               "[who=" + player.who() +
                               ", turnHolder=" + _gameobj.turnHolder + "].");
  
        // make sure this is legal 
        } else if (	_logic.isLegalMove(nextGameCharacterLocation, gameCharacter) && 
        			_logic.canAttack(_gameobj.gameCharacters, nextGameCharacterLocation) ) {
        	//move character to location.
        	gameCharacter.x = nextGameCharacterLocation.x;
        	gameCharacter.y = nextGameCharacterLocation.y;
        	_gameobj.updateGameCharacters(gameCharacter);
        	
        	//attack
        	doAttack(gameCharacter);
        	
        	_turndel.endTurn();
  
        } else {
        	Log.info("illegal move");
            System.err.println("Received illegal move request " +
                               "[who=" + player.who() +
                               ", piece=" + gameCharacter + "].");
        }

    }
    
    public void endTurn(	BodyObject player, 
							SurvivalObject.GameCharacter nextGameCharacterLocation, 
							SurvivalObject.GameCharacter gameCharacter)
    {

        // make sure it's this player's turn
        int pidx = _turndel.getTurnHolderIndex();
        if (_playerOids[pidx] != player.getOid()) {
            System.err.println("Requested to place piece by non-turn holder " +
                               "[who=" + player.who() +
                               ", turnHolder=" + _gameobj.turnHolder + "].");
  
        // make sure this is legal 
        } else if (_logic.isLegalMove(nextGameCharacterLocation, gameCharacter)) {
        	Log.info("legal move, end turn");

        	//move character to location.
        	gameCharacter.x = nextGameCharacterLocation.x;
        	gameCharacter.y = nextGameCharacterLocation.y;
        	_gameobj.updateGameCharacters(gameCharacter);
        	
        	_turndel.endTurn();
        } else {
        	Log.info("illegal move");
            System.err.println("Received illegal move request " +
                               "[who=" + player.who() +
                               ", piece=" + gameCharacter + "].");
        }
    }
    
    /**
     * actually perform the mining action, giving the player points
     *
     * @param miningCharacter the mining character
     * @return int the number of points earned
     */
    public int doMine(SurvivalObject.GameCharacter miningCharacter)
    {
    	miningCharacter.currentPoints += (miningCharacter.killStreak+1);
    	_gameobj.updateGameCharacters(miningCharacter);

    	//set notification message
    	String text = miningCharacter.playerOwner + " mined and earned "+(miningCharacter.killStreak+1)+" pts";
    	String prevMessage = "";
        if(_gameobj.matchPraise != null){
            prevMessage = _gameobj.matchPraise.text;        	
        }
        _gameobj.setMatchPraise(new SurvivalObject.MatchPraise(0, 0, 0, prevMessage+" \n "+text));    	
    	
    	return miningCharacter.killStreak+1;
    }

    /**
     * A game character attacks, earning points, killing other characters, and making players stronger
     * NOTE: Edge case when there's multiple characters around to attack.
     * Right now it's just first one picked, should be smarter about it.
     *
     * @param attackingCharacter the attacking character
     * @return the points earned. WARNING: returns current damage if an AI attacked.
     */
    public int doAttack(SurvivalObject.GameCharacter attackingCharacter )
    {
    	SurvivalObject.GameCharacter victimCharacter = _logic.attackWhichCharacter(_gameobj.gameCharacters, attackingCharacter); 
    	
    	//hurt the victim.
    	victimCharacter.currentHealth -= attackingCharacter.currentDamage;
    	
    	//set notification message
    	String text = attackingCharacter.playerOwner + " attacked "+victimCharacter.playerOwner;
    	if(attackingCharacter.playerOwner.equals(new Name(SurvivalObject.CREEPER_NAME)) == false ){
    		text += " and earned "+attackingCharacter.currentDamage+" pts";
    	}
    	if(victimCharacter.currentHealth <= 0){
    		text += " killing "+victimCharacter.playerOwner;
    	}
    	String prevMessage = "";
        if(_gameobj.matchPraise != null){
            prevMessage = _gameobj.matchPraise.text;        	
        }    	
    	
        //if the victim is out of health, kill the character
    	if(victimCharacter.currentHealth <= 0){
    		characterKilled(victimCharacter);
    		attackingCharacter.killStreak++;
    	}
    	//add points to players and make them stronger
    	if(attackingCharacter.playerOwner.equals(new Name(SurvivalObject.CREEPER_NAME)) == false ){
    		attackingCharacter.currentPoints += attackingCharacter.currentDamage;
    		attackingCharacter.currentDamage++;
    		attackingCharacter.currentHealth++;
    	}
    	
    	//register changes on the distributed object and display information on the view
    	_gameobj.updateGameCharacters(attackingCharacter);
    	_gameobj.updateGameCharacters(victimCharacter);
        _gameobj.setMatchPraise(new SurvivalObject.MatchPraise(0, 0, 0, prevMessage+" \n "+text));    	
    	
        return attackingCharacter.currentDamage;
    }
    
    /**
     * Kill a character, moving it to a random location and resetting stats 
     *
     * @param victimCharacter the victim character
     */
    public void characterKilled(SurvivalObject.GameCharacter victimCharacter)
    {
    	victimCharacter.currentHealth = 5;
    	victimCharacter.currentDamage = 1;
    	victimCharacter.killStreak = 0;
    	victimCharacter.deaths++;
    	victimCharacter.x = (int)(Math.random()*SurvivalObject.GAME_SIZE);
    	victimCharacter.y = (int)(Math.random()*SurvivalObject.GAME_SIZE);
    	_gameobj.updateGameCharacters(victimCharacter);
    }
    
    /**
     * Trigger an act of god, killing the strongest character
     *
     * @param player the player
     */
    public void triggerActOfGod(
    		BodyObject player
	  ){
    	//Which act of god?
		SurvivalLogic.ActOfGod actOfGod = _logic.chooseRandomDeathEvent();    	
    	
    	//find player with the most health
    	SurvivalObject.GameCharacter healthiestCharacter = _gameobj.getHealthiestCharacter();    	
    	//FINISH HIM!
    	//Note that damage is actually irrelevant. It's there just in case I want to expand the mechanic later
    	characterKilled(healthiestCharacter);

    	//set notification message
        String text = healthiestCharacter.playerOwner.toString() + " " + actOfGod.description;
        String prevMessage = "";
        if(_gameobj.matchPraise != null){
            prevMessage = _gameobj.matchPraise.text;        	
        }
        _gameobj.setMatchPraise(new SurvivalObject.MatchPraise(0, 0, 0, prevMessage+" \n "+text));
	}
    

    /** Our game object. */
    protected SurvivalObject _gameobj;

    /** Our game configuration. */
    protected ToyBoxGameConfig _gameconf;
    
    protected int _gameTurnLength;
    protected int _currentGameTurn;
    protected double PercentageGameComplete; 
    
    /** Handles our turn based game flow. */
    protected TurnGameManagerDelegate _turndel;	
    
    /** Used to determine legality of moves, etc. */
    protected SurvivalLogic _logic;

}
